package com.sikaryofficial.workbench.utils.excel.converter;

import cn.hutool.core.bean.BeanDesc;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.PropDesc;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.sikaryofficial.workbench.utils.excel.annotation.ExcelEnum;
import com.sikaryofficial.workbench.utils.excel.utils.StringValueConverter;
import lombok.Data;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 从 ExcelEnum 注解中获取枚举类信息进行转换
 * @param <T>
 */
public abstract class AbstractEnumStringConverter<T> implements Converter<T>, StringValueConverter<T> {

    private final LoadingCache<EnumCacheKey, Map<String, String>> cache;

    protected AbstractEnumStringConverter() {
        cache = Caffeine.newBuilder()
                //最后一次写入后经过固定时间过期
                .expireAfterWrite(60, TimeUnit.SECONDS)
                //maximumSize=[long]: 缓存的最大条数
                .maximumSize(1000)
                .build(this::buildEnumMap);
    }

    /**
     * 提取枚举类所有对象中的指定字段值生成map
     * @return  code字段值-文本字段值 map
     */
    private Map<String, String> buildEnumMap(EnumCacheKey cacheKey) {
        Enum<?>[] enumConstants = cacheKey.enumClass.getEnumConstants();

        BeanDesc beanDesc = BeanUtil.getBeanDesc(cacheKey.enumClass);
        PropDesc codePropDesc = beanDesc.getProp(cacheKey.codeFieldName);
        PropDesc textPropDesc = beanDesc.getProp(cacheKey.textFieldName);

        if (codePropDesc == null || textPropDesc == null) {
            throw new RuntimeException("enumClass not contains codeFieldName or textFieldName");
        }
        Map<String, String> result = new HashMap<>();
        for (Enum<?> enumConstant : enumConstants) {
            Object codeProp = codePropDesc.getValue(enumConstant);
            Object textProp = textPropDesc.getValue(enumConstant);
            result.put(codeProp + "", textProp + "");
        }
        return result;
    }

    @Data
    public static class EnumCacheKey {
        private Class<? extends Enum<?>> enumClass;
        private String codeFieldName;
        private String textFieldName;

        public EnumCacheKey(ExcelEnum excelEnum) {
            this(excelEnum.value(), excelEnum.code(), excelEnum.text());
        }
        public EnumCacheKey(Class<? extends Enum<?>> enumClass, String codeFieldName, String textFieldName) {
            this.enumClass = enumClass;
            this.codeFieldName = codeFieldName;
            this.textFieldName = textFieldName;
        }
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    public T convertToJavaData(ReadConverterContext<?> context) throws Exception {
        ReadCellData<?> readCellData = context.getReadCellData();
        String stringValue = readCellData.getStringValue();

        Field field = context.getContentProperty().getField();
        ExcelEnum excelEnum = getExcelEnumAnnotation(field);
        Map<String, String> code2TextMap = cache.get(new EnumCacheKey(excelEnum));

        for (Map.Entry<String, String> entry : code2TextMap.entrySet()) {
            if (entry.getValue().equals(stringValue)) {
                return fromStr(entry.getKey());
            }
        }
        if (excelEnum.errorOnMissMatch()) {
            throw new RuntimeException("未知数值：" + stringValue);
        }
        return null;
    }

    @Override
    public WriteCellData<?> convertToExcelData(WriteConverterContext<T> context) throws Exception {
        T value = context.getValue();
        Field field = context.getContentProperty().getField();
        ExcelEnum excelEnum = getExcelEnumAnnotation(field);
        Map<String, String> code2TextMap = cache.get(new EnumCacheKey(excelEnum));
        return new WriteCellData<>(code2TextMap.getOrDefault(toStr(value), ""));
    }

    private ExcelEnum getExcelEnumAnnotation(Field field) {
        Annotation[] annotations = field.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof ExcelEnum) {
                return (ExcelEnum) annotation;
            }
        }
        throw new RuntimeException("ExcelEnumConverter: ExcelEnum not found");
    }

}
